package com.cskaoyan.threadsafe.edition3;

/*
        简单分析下，出现上述问题的原因：
            1.多线程运行环境
            2.数据共享
            3.共享数据的非原子操作:
               所谓的原子操作，多个操作作为一个整体，一次完成，要么都做，要么都不做

       如何解决问题呢？ 打破上述三个条件中的任意一个即可
       1. 多线程运行环境 改变不了，需求决定
       2. 访问共享数据  改变不了，需求决定
       3. 共享数据的非原子操作: 只要把对共享数据的非原子操作，变成原子操作即可

       接下来的问题就变成了，如何定义原子操作的问题?
           思路1：如果我们能在一个线程，对共享变量的一组操作的执行过程，能够阻止线程切换，
                  那么很自然的，这一组操作就变成了原子操作。
                  但是思路1我们实现不了： 抢占式线程调度，代码层面(线程中执行的代码)，无法控制线程调度

           思路2：我们无法阻止线程切换，但是我们换个思路，我们给共享变量，加一把锁，利用锁来实现原子操作
                  使用锁，可以给共享变量加锁，从而保证：
                    a. 只有加锁的线程，能够访问到共享变量，而且一把锁同时只能被一个线程加锁
                    b. 而且，在加锁线程，没有完成对共享共享变量的，一组操作之前，不会释放锁，
                    c. 只要不释放锁.其他线程，即使被调度执行，也无法访问共享变量


          java语言层面，是通过定义synchronized代码块，来实现原子操作
          synchronized(锁对象) {
            执行的访问共享变量(原子操作)
          }


  同步代码块的细节：
    a. synchronized代码块中的锁对象，可以是java语言中的任意对象(java语言中的任意一个对象，都可以充当锁的角色仅限于synchronized代码块中)：
        1)因为java中所有对象，内部都存在一个标志位，表示加锁和解锁的状态
        2)所以其实锁对象，就充当着锁的角色
        所谓的加锁解锁，其实就是设置随对象的标志位，来表示加锁解锁的状态。

    b.  我们的代码都是在某一条执行路径(某一个线程中运行)，当某个线程执行到同步代码块时，
        会尝试在当前线程中，对锁对象加锁
        1） 此时，如果锁对象处于未加锁状态，jvm就会设置锁对象的标志位(加锁)，并在锁对象中记录，是哪个线程加的锁
            然后，让加锁成功的当前线程，执行同步代码块中的代码

        2） 此时，如果锁对象已经被加锁，且加锁线程不是当前线程，系统会让当前线程处于阻塞状态(等着)，
            直到加锁线程，执行完了对共享变量的一组操作，并释放锁


    c. 加锁线程何时释放锁？
       当加锁线程，执行完了同步代码块中的代码(对共享变量的一组操作)，在退出同步代码块之前，
       jvm自动清理锁对象的标志位，将锁对象变成未上锁状态(释放锁)



 */
public class Demo1 {

    public static void main(String[] args) {
        // 创建售票任务对象
        SalesTask salesTask = new SalesTask();

        Thread firstWindow = new Thread(salesTask, "first");
        Thread secondWindow = new Thread(salesTask, "second");
        Thread thirdWindow = new Thread(salesTask, "third");

        // 启动三个线程，模拟三个窗口售票
        firstWindow.start();
        secondWindow.start();
        thirdWindow.start();
    }
}


class SalesTask implements Runnable {

    //待售卖的100张票
    // 普通成员变量
    int tickets = 100;

    @Override
    public void run() {

        //synchronized (this) {
        //  售卖100张票
        while (tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //  通过synchronized代码块解决多线程的数据安全问题
            // this 指的是在三个线程中执行的，salesTask对象(售票任务)
            synchronized (this) {
                // 判断是否有票  double check
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "售出了第" + tickets-- + "张票");
                }
            }
        }
        // }
    }
}
